Go语言基础知识点

Go语言基础知识要点.

变量、函数

  • 函数名称大写则包外可见,小写不可见
  • 变量声明未赋值时是零值,数字是0,布尔是false,字符串是””,接口和引用类型是nil(slice、指针、map、通道、函数)
  • 短变量声明语句中,只需要声明至少一个新变量即可:
1
2
a := 1
a, b := F()
  • 类型转换分为强制类型转换及类型断言

    • 强制类型转换格式为T(x)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 基本类型转换
      var a int32 = 1
      var b int64 = int64(a)
      var c float64 = 12.3
      var d float32 = float32(c) // 丢精度
      // 指针类型
      var a int = 10
      var p *int = &a
      var c *int64 = (*int64)(unsafe.Pointer(b)) // 需要借助unsafe包
    • 类型断言

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // x.(type),仅限于switch语句
      var a interface{} = 10
      switch a.(type) {
      case int:
      fmt.Println("int")
      case float32:
      fmt.Println("float32")
      }
      // 类型转换检验
      var a interface{} = 10
      if t, ok := a.(int); ok {
      fmt.Println("int ", t)
      }
  • 内置new(T)函数作用:初始化变量,并返回其指针;与局部变量声明没区别,为了分配内存

  • 包初始化init()函数:不能被调用,按照每个包的声明顺序执行
  • 字符串相关标准包:strings搜索替换比较切分等;bytes与strings类似但作用于字节slice;strconv用于字符串与其他类型的转换;unicode判断文字特性如大小写、是否为数字等

slice、map、chan

  • slice三属性:指针、长度、容量;指针指向第一个可访问的元素;长度指slice中元素个数,len获取;容量指slice的底层数组的长度,cap获取
  • 严格来说golang中没有引用传递,只有值传递以及“使用引用作为值”的值传递
    • 数组作为函数参数时是值传递,使用其指针可以认为是传递引用值;slice本身包含指针,因此传递引用值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
arr := [3]int{1,2,3}
// 值传递
f1 := func(arr [3]int) {
arr[0] = 100// do something with arr
}
f1(arr) // arr不变
// 引用的值的传递
f2 := func(arr *[3]int) {
arr[0] = 100 // do something with arr
}
f2(arr) // arr变了
// 引用的值的传递
s := arr[:]
f3 := func(s []int) {
s[0] = 100 // do something with slice
}
f3(s) // slice变了
//
- map和channel的传递也是值传递
1
2
3
4
5
6
7
8
9
10
11
12
m := map[string]int{"a": 1}
f := func(m map[string]int) {
fmt.Printf( "map addr in func: %p\n",&m)
m["a"]= 100
}
fmt.Printf("old map: %+v\n", m)
p := &m
fmt.Printf("original map addr: %p\n", p)
f(m)
fmt.Printf("new map: %+v\n", m)
// 可以看到,函数外中p的地址与函数内的地址不一样
// 因此,传递的是p的值(具体地址值)的拷贝,且func(m map)等效于func(p *map),语言层面做了封装
  • slice、map、channel一定要make来初始化的原因:均含有指针属性,只声明的话变量都是nil
1
2
3
4
5
6
var s []int // nil
var m map[string]string // nil
var c chan int // nil
var s1 = make([]int, 0) // not nil, len = 0
var m1 = make(map[string]string, 0) // not nil, len = 0
var c1 = make(chan int, 0) // not nil, len = 0

错误处理

  • 错误处理要自己建立,利用panic、recover和defer
    • defer是后进先出,且defer在return后、函数出栈前执行,因此可以改变返回值
    • handler实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
func main() {
errorHandler(business)()
}
func business() {
rand.Seed(time.Now().Unix())
r := rand.Intn(100) // 随机数模拟业务中不同的出错情况
if r%3 == 1 {
check(a())
} else if r%3 == 2 {
check(b())
}
}
func a() error {
return errors.New("error in a")
}
func b() error {
return errors.New("error in b")
}
func check(err error) {
if err != nil {
panic(err)
}
}
// 封装f:为传入的函数增加defer
func errorHandler(f func()) func() {
return func() {
defer func() {
if r := recover(); r != nil {
stack := make([]byte, 1024*8)
stack = stack[:runtime.Stack(stack, false)] // 打印函数栈信息
log.Printf("got error: %+v, stack: %s", r, string(stack))
}
}()
f()
}
}
- Try-Catch实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
// usage
TryCatch(func() {
// do sth.
Throw("msg")
}, func(e interface{}) {
// Exception caught
})
*/

// define
func Try(try func()) interface{} {
var ret interface{} = nil
TryCatch(try, func(e interface{}) {
ret = e
})
return ret
}

func TryAndCatchError(try func()) error {
e := Try(try)
return ToError(e)
}

func TryCatch(try func(), catch func(e interface{})) {
defer func() {
if e := recover(); e != nil {
catch(e)
}
}()
try()
}

func Throw(e interface{}) {
panic(e)
}

func ThrowErrorf(msg string, args ...interface{}) {
err := fmt.Errorf(msg, args...)
Throw(err)
}

func ThrowIfErrorNotNil(err error) {
if err != nil {
Throw(err)
}
}

func ToError(e interface{}) error {
if e == nil {
return nil
}
switch e.(type) {
case error:
return e.(error)
}
return fmt.Errorf("%s", e)
}

方法、接口

  • 函数vs方法:方法多了一个接收者,表示该方法与此接收者类型绑定;值接收者不能改变原来的类型变量,指针接收者可以改变
  • 接口即约定,是一组包含方法名、参数、返回值但无具体实现的方法的集合
    • 实现接口的所有方法则认为实现了接口,无需显式声明
    • 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type MyInterface interface {
MyInt() int
MyString() string
}
type MyStruct struct {
a int
b string
}
func (s MyStruct) MyInt() int {
return s.a
}
func (s MyStruct) MyString() string {
return s.b
}
func main() {
s := MyStruct{0, "b"}
var i MyInterface
i = s // 赋值给接口类型的变量
fmt.Printf("%d %s", i.MyInt(), reflect.TypeOf(i))
}
- 接口断言判断类型是否实现了某接口
1
2
3
4
5
6
7
8
9
10
// 使用特殊的接口断言x.(T),判断是否实现了接口
func CheckInterface(test interface{}) {
if _, ok := test.(MyInterface); ok {
fmt.Printf("interface implemented\n")
}
}
func main() {
s := MyStruct{0, "b"}
CheckInterface(s)
}
  • 空接口不包含任何方法的接口,所有类型都实现了空接口,因此可以用来存储任何类型的数值
  • 接口零值是nil
  • 接口嵌套
1
2
3
4
5
6
7
8
type MyInterface interface {
MyInt() int
MyString() string
}
type MyLargerInterface interface {
MyInterface
MyOtherType() interface{}
}
  • 接口用途之一:类型变量初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
type People interface {
ID() string
Name() string
}
// 类型首字母小写不导出,强制要求使用函数来初始化
type people struct {
id string
name string
}
func (p people) ID() string {
return p.id
}
func (p people) Name() string {
return p.name
}
func NewPeople() People {
return people{
id: "random id",
name: "empty name",
}
}
// 其他包中调用
func funcInOtherPkg() {
// p := people{}不能直接用,因为people类型没有导出
randomP := NewPeople()
fmt.Printf("%s, %s\n", randomP.ID(), randomP.Name())
realP := NewPeopleWithParam("1", "real name")
fmt.Printf("%s, %s\n", realP.ID(), realP.Name())
}